# /**
# * Copyright (c) 2022 Beijing Jiaotong University
# * PhotLab is licensed under [Open Source License].
# * You can use this software according to the terms and conditions of the [Open Source License].
# *
# * You may obtain a copy of [Open Source License] at: [https://open.source.license/]
# *
# * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# *
# * See the [Open Source License] for more details.
# */
# /**
# * Author: Shihao Xie
# * Created: Mar. 18, 2022
# * Supported by: National Key Research and Development Program of China
# */
from phot_server.core.exception import IdDuplicationException, ResourceNotFoundError, ParameterValueError
from phot_server.rpc.device_data_type.ttypes import PhotGrid, PhotWaveguide, PhotSource, PhotDetector
from phot_server.utils.singleton import singleton


# temporarily storage fdtd data
@singleton
class GridRepository:

    def __init__(self):
        self.__grids = dict()
        self.__fdtd_grids = dict()
        self.__grid_figure_history = {}

    # Grid interfaces
    def add_grid(self, grid: PhotGrid):
        if self.has_grid(grid.gridId) is False:
            self.__parameter_to_attributes(grid)
            self.__grids[grid.gridId] = grid
        else:
            raise IdDuplicationException(PhotGrid.__name__, grid.gridId)

    def get_grid(self, grid_id) -> PhotGrid:
        grid = self.__grids.get(grid_id, None)
        if grid is not None:
            return grid
        else:
            raise ResourceNotFoundError(PhotGrid.__name__, grid_id)

    def get_grids(self) -> dict:
        return self.__grids

    def get_all_grid(self) -> list:
        return [grid for grid in self.__grids.values()]

    def get_all_grid_id(self) -> list:
        return [grid_id for grid_id in self.__grids.keys()]

    def set_grid(self, grid: PhotGrid):
        if self.has_grid(grid.gridId):
            self.__parameter_to_attributes(grid)
            self.__grids.update({grid.gridId: grid})
        else:
            raise ResourceNotFoundError(PhotGrid.__name__, grid.gridId)

    def update_grid_attrs(self, grid_id, updated_parameters: dict):
        grid = self.get_grid(grid_id)
        for key, value in updated_parameters.items():
            if hasattr(grid, key) is False:
                raise ResourceNotFoundError(message="Could not find parameter '{}' in grid '{}'".format(key, grid_id))
            elif isinstance(value, (list, dict)):
                pass
                # raise ParameterValueError("Could not update parameters that its type is '{}'.".format(type(value)))
            elif value is not None and value != getattr(grid, key):
                setattr(grid, key, value)
        self.__parameter_to_attributes(grid)

    def delete_grid(self, grid_id):
        if self.has_grid(grid_id):
            self.__grids.pop(grid_id)
        else:
            raise ResourceNotFoundError(PhotGrid.__name__, grid_id)

    def has_grid(self, grid_id) -> bool:
        return True if self.__grids.get(grid_id, None) is not None else False

    # Light source interfaces

    def add_source(self, grid_id, source: PhotSource):
        if self.has_source(grid_id, source.sourceId) is False:
            self.__parameter_to_attributes(source)
            self.get_grid(grid_id).sources.append(source)
        else:
            raise IdDuplicationException(PhotSource.__name__, source.sourceId, PhotGrid.__name__, grid_id)

    def get_source(self, grid_id, source_id, raise_error=True):
        for source in self.get_grid(grid_id).sources:
            if source.sourceId == source_id:
                return source
        if raise_error is True:
            raise ResourceNotFoundError(PhotSource.__name__, source_id, PhotGrid.__name__, grid_id)
        return None

    def set_source(self, grid_id, source: PhotSource):
        self.delete_source(grid_id, source.sourceId)
        self.__parameter_to_attributes(source)
        self.get_grid(grid_id).sources.append(source)

    def get_all_source(self, grid_id) -> list:
        return self.get_grid(grid_id).sources

    def get_all_source_id(self, grid_id) -> list:
        return [source.sourceId for source in self.get_grid(grid_id).sources]

    def delete_source(self, grid_id, source_id):
        source = self.get_source(grid_id, source_id)
        self.get_grid(grid_id).sources.remove(source)

    def has_source(self, grid_id, source_id):
        return True if self.get_source(grid_id, source_id, False) is not None else False

    # Waveguide interface
    def add_waveguide(self, grid_id, waveguide: PhotWaveguide):
        if self.has_waveguide(grid_id, waveguide) is False:
            self.__parameter_to_attributes(waveguide)
            self.get_grid(grid_id).waveguides.append(waveguide)
        else:
            raise IdDuplicationException(PhotWaveguide.__name__, waveguide.waveguideId, PhotGrid.__name__, grid_id)

    def get_waveguide(self, grid_id, waveguide_id, raise_error=True):
        for waveguide in self.get_grid(grid_id).waveguides:
            if waveguide.waveguideId == waveguide_id:
                return waveguide
        if raise_error:
            raise ResourceNotFoundError(PhotWaveguide.__name__, waveguide_id, PhotGrid.__name__, grid_id)
        return None

    def set_waveguide(self, grid_id, waveguide: PhotWaveguide):
        self.delete_waveguide(grid_id, waveguide.waveguideId)
        self.__parameter_to_attributes(waveguide)
        self.add_waveguide(grid_id, waveguide)

    def get_all_waveguide(self, grid_id):
        return self.get_grid(grid_id).waveguides

    def get_all_waveguide_id(self, grid_id) -> list:
        return [waveguide.waveguideId for waveguide in self.get_grid(grid_id).waveguides]

    def delete_waveguide(self, grid_id, waveguide_id):
        waveguide = self.get_waveguide(grid_id, waveguide_id)
        self.get_grid(grid_id).waveguides.remove(waveguide)

    def has_waveguide(self, grid_id, waveguide_id):
        return True if self.get_waveguide(grid_id, waveguide_id, False) is not None else False

    # fdtd grid
    def add_fdtd_grid(self, grid_id, grid):
        self.__fdtd_grids[grid_id] = grid

    def get_fdtd_grid(self, grid_id):
        grid = self.__fdtd_grids.get(grid_id, None)
        if grid is not None:
            return grid
        else:
            # raise ResourceNotFoundError(message="{} with id '{}' should been run first."
            #                             .format("Grid", grid_id))
            return None

    def delete_fdtd_grid(self, grid_id):
        self.__fdtd_grids.pop(grid_id)

    def get_detector(self, grid_id, detector_id, raise_error=True):
        for detector in self.get_grid(grid_id).detectors:
            if detector.detectorId == detector_id:
                return detector
        if raise_error:
            raise ResourceNotFoundError(PhotWaveguide.__name__, detector_id, PhotGrid.__name__, grid_id)
        return None

    def add_detector(self, grid_id, detector: PhotDetector):
        if self.has_detector(grid_id, detector.detectorId):
            raise IdDuplicationException(PhotDetector.__name__, detector.detectorId, PhotGrid.__name__, grid_id)
        else:
            self.__parameter_to_attributes(detector)
            self.get_grid(grid_id).detectors.append(detector)

    def set_detector(self, grid_id, detector: PhotDetector):
        self.delete_detector(grid_id, detector.detectorId)
        self.__parameter_to_attributes(detector)
        self.add_detector(grid_id, detector)

    def get_all_detector(self, grid_id):
        return self.get_grid(grid_id).detectors

    def get_all_detector_id(self, grid_id) -> list:
        return [detector.detectorId for detector in self.get_grid(grid_id).detectors]

    def delete_detector(self, grid_id, detector_id):
        detector = self.get_detector(grid_id, detector_id)
        self.get_grid(grid_id).detectors.remove(detector)

    def has_detector(self, grid_id, detector_id):
        return True if self.get_detector(grid_id, detector_id, False) is not None else False

    def add_fdtd_figure_log(self, grid_id, figure_paths: list):
        history = self.__grid_figure_history.get(grid_id, None)
        if history is not None:
            self.__grid_figure_history[grid_id].extend(figure_paths)
        else:
            self.__grid_figure_history[grid_id] = figure_paths

    def get_figure_log_index(self, grid_id):
        history = self.__grid_figure_history.get(grid_id, None)
        if history is not None:
            return len(history)
        else:
            return 0

    def get_fdtd_figure_logs(self, grid_id):
        return self.__grid_figure_history.get(grid_id, [])

    def delete_fdtd_figure_logs(self, grid_id):
        return self.__grid_figure_history.pop(grid_id, None)

    def __parameter_to_attributes(self, obj):
        from json import loads
        if not hasattr(obj, "parameters") or obj.parameters is None or obj.parameters == "":
            return
        parameters = loads(obj.parameters)
        if not isinstance(parameters, dict):
            return
        # name: str = obj.__class__.__name__
        # reversed_map = {v: k for k, v in map.items()}
        for key, value in parameters.items():
            # if key in reversed_map.keys():
            #     attribute_name = reversed_map[key]
            #     setattr(obj, attribute_name, value)
            if hasattr(obj, key) and value is not None:
                setattr(obj, key, value)